import asyncio
import re
from typing import Any, Dict, Union, List, final
from bs4 import BeautifulSoup
import httpx
from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp.prompts import base

# Initialize FastMCP server
mcp = FastMCP("limitLessTcg")
COOKIES_LIMIT = {
        '_ga': 'GA1.1.268084582.1716518500',
        '_sharedid': '441311a1-369c-4aac-9a23-c05fc1892ffb',
        'idw-fe-id': 'af98fea2-5307-43a2-a93c-e905a934cb88',
        '_ga_0CPE0JFSCT': 'GS1.1.1718543633.2.0.1718543633.0.0.0',
        '_ga_JEXRLS78TW': 'GS1.1.1732324211.1.0.1732324211.0.0.0',
        '_ga_2127HG4038': 'deleted',
        '_ga_V9R741MHMH': 'GS1.1.1745465876.24.1.1745465924.0.0.0',
        '_ga_2127HG4038': 'GS1.1.1745461795.72.1.1745465938.0.0.0',
    }
async def fetch_pokemon_decks(format, time, rank, variants, page):
    cookies = COOKIES_LIMIT

    headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
        'cache-control': 'no-cache',
        'pragma': 'no-cache',
        'priority': 'u=0, i',
        'referer': 'https://limitlesstcg.com/decks?format=SVI-JTG&time=all&rank=wins',
        'sec-ch-ua': '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'document',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'same-origin',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0',
    }

    params = {
        'format': format,
        'time': time,
        'rank': rank,
        'page': page
    }

    # 仅在 variants 不是 'false' 时添加该参数,该请求只要带variants就一定为true
    if variants != 'false':
        params['variants'] = variants

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(
                'https://limitlesstcg.com/decks',
                params=params,
                cookies=cookies,
                headers=headers,
                timeout=30.0
            )
            response.raise_for_status()
            return response.text  # 返回文本内容（与原代码行为一致）
        except Exception as e:
            print(f"Request failed: {e}")
            return None

async def fetch_tools_drawcalc(basics, good, bad, outs):
    cookies = COOKIES_LIMIT

    headers = {
        'accept': '*/*',
        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
        'cache-control': 'no-cache',
        'content-type': 'application/json',
        'pragma': 'no-cache',
        'priority': 'u=1, i',
        'referer': 'https://limitlesstcg.com/tools/drawcalc',
        'sec-ch-ua': '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0'
    }

    params = {
        'basics': str(basics)
    }

    # 仅在 variants 不是 'false' 时添加该参数,该请求只要带variants就一定为true
    if good != 0:
        params['good'] = str(good)
    if bad != 0:
        params['bad'] = str(bad)
    if outs != 0:
        params['outs'] = str(outs)

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(
                'https://limitlesstcg.com/api/tools/drawcalc',
                params=params,
                cookies=cookies,
                headers=headers,
                timeout=30.0
            )
            response.raise_for_status()
            return response.json()  # 返回文本内容（与原代码行为一致）
        except Exception as e:
            print(f"Request failed: {e}")
            return None

async def fetch_deck_core(deck_core_url):
    cookies = COOKIES_LIMIT

    headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
        'cache-control': 'no-cache',
        'pragma': 'no-cache',
        'priority': 'u=0, i',
        'referer': 'https://limitlesstcg.com/decks?format=SVI-JTG&time=all&rank=wins',
        'sec-ch-ua': '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'document',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'same-origin',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0',
    }

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(
                url=deck_core_url,
                cookies=cookies,
                headers=headers,
                timeout=30.0
            )
            response.raise_for_status()
            return response.text  # 返回文本内容（与原代码行为一致）
        except Exception as e:
            print(f"Request failed: {e}")
            return None

async def fetch_deck_list(deck_list_url):
    cookies = COOKIES_LIMIT

    headers = {
        'accept': '*/*',
        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
        'cache-control': 'no-cache',
        'content-type': 'application/json',
        'pragma': 'no-cache',
        'priority': 'u=1, i',
        'referer': 'https://limitlesstcg.com/decks/284?variant=3',
        'sec-ch-ua': '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0'
    }

    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(
                deck_list_url,
                cookies=cookies,
                headers=headers,
                timeout=30.0
            )
            response.raise_for_status()
            return response.text  # 返回文本内容（与原代码行为一致）
        except Exception as e:
            print(f"Request failed: {e}")
            return None

def extract_pokemon_data(html_str):
    soup = BeautifulSoup(html_str, 'html.parser')
    tr_list = soup.find_all('tr')
    results = []

    for tr in tr_list:
        tds = tr.find_all('td')
        if len(tds) < 5:
            continue

        # 提取卡组名称和链接
        link_td = tds[2]
        a_tag = link_td.find('a')
        if not a_tag:
            continue

        # ------ 新增名称处理逻辑 ------
        # 判断是否存在 <span class="annotation">
        annotation_span = a_tag.find('span', class_='annotation')

        if annotation_span:
            # 情况1：带注解的名称（如 Gardevoir ex），保留完整名称
            name = a_tag.get_text()
        else:
            # 情况2：多词名称（如 Dragapult Dusknoir），拆分后用、连接
            name_parts = a_tag.get_text(strip=True).split()
            name = '、'.join(name_parts)
        # ------ 名称处理结束 ------

        # 处理链接特殊符号并添加域名
        deck_link = f"https://limitlesstcg.com{a_tag['href']}"

        # 提取得分和分享率
        total_score = tds[3].get_text(strip=True)
        share_rate = tds[4].get_text(strip=True)

        # 组合结果字符串（移除链接外的冗余引号）
        result = f"核心宝可梦：{name} 总计得分：{total_score} 分享率：{share_rate} 构筑链接：<{deck_link}>"
        results.append(result)

    return '\n'.join(results)

def format_percentage(s):
    num = float(s)
    return f"{num * 100:.2f}%"

def format_tools_drawcalc(result: dict):
    format_dict = {
        'mulligan': "起手没有基础宝可梦",
        'multiple': "起手至少包含2张基础宝可梦",
        'good': "起手至少1张优质基础宝可梦",
        'bad': "起手仅有劣质基础宝可梦",
        'x': "起手至少包含1张支援者卡"
    }
    formatStr = []
    for key, value in result.items():
        formatStr.append(f"{format_dict[key]}--概率：{format_percentage(value)}")
    return '\n'.join(formatStr)

async def fetch_card_detail(client: httpx.AsyncClient, card_str_id: str) -> Union[str, None]:
    """单个卡组请求函数（带错误处理）"""
    url = f"https://limitlesstcg.com/api{card_str_id}"
    try:
        response = await client.get(
            url,
            cookies=COOKIES_LIMIT,
            headers={
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
        'cache-control': 'no-cache',
        'pragma': 'no-cache',
        'priority': 'u=0, i',
        'referer': 'https://limitlesstcg.com/decks?format=SVI-JTG&time=all&rank=wins',
        'sec-ch-ua': '"Microsoft Edge";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'document',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'same-origin',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0',
    },
            params={'lang':'en'},
            timeout=15.0  # 单独请求超时时间
        )
        response.raise_for_status()
        return response.json()
    except httpx.HTTPStatusError as e:
        print(f"请求 {card_str_id} 失败，状态码：{e.response.status_code}")
    except Exception as e:
        print(f"请求 {card_str_id} 异常：{str(e)}")
    return None

async def batch_fetch_decks(card_id_list: list, concurrency: int = 5) -> Dict:
    """批量请求卡组数据（带并发控制）"""
    results = {}

    # 创建异步客户端（保持长连接）
    async with httpx.AsyncClient(
            limits=httpx.Limits(max_connections=concurrency)
    ) as client:
        # 生成任务列表
        tasks = [
            (card_str_id, asyncio.create_task(fetch_card_detail(client, card_str_id)))
            for card_str_id in card_id_list
        ]

        # 按任务创建顺序收集结果
        for card_str_id, task in tasks:
            try:
                # 设置全局超时（所有任务总超时时间）
                card_detail = await asyncio.wait_for(task, timeout=60.0)
                results[card_str_id] = card_detail
            except asyncio.TimeoutError:
                print(f"请求 {card_str_id} 超时终止")
                results[card_str_id] = None
            except Exception as e:
                print(f"处理 {card_str_id} 时发生意外错误: {str(e)}")
                results[card_str_id] = None

    return results

async def extract_deck_core(html_str):
    soup = BeautifulSoup(html_str, 'html.parser')
    # 初始化核心卡数据存储、顶尖玩家卡组链接
    core_card_href = {}
    best_player_deck = []
    # 结果字符列表
    core_cards_format_list = []
    # 提取名称
    name = soup.find('h1', class_='name').get_text(strip=True)
    core_card_items = soup.find_all(class_='core-card')
    if len(core_card_items) < 1:
        core_cards_format_list.append("暂时无法展示核心构筑卡，没有足够的对战数据支持")
    else:
        # 提取核心卡牌链接与大数据统计
        for ele in soup.find_all(class_='core-card'):
            href = ele.find(class_='flex').get('href', None)
            data_tooltip = ele.find(class_='share').get('data-tooltip', None)
            if not href or not data_tooltip:
                continue
            else:
                core_card_href[href] = data_tooltip

        card_detail_dict = await batch_fetch_decks([key for key in core_card_href.keys()], concurrency=5)
        for key,value in card_detail_dict.items():
            core_cards_format_list.append(
                f"{value['name']} | 使用统计 :{core_card_href[key]}"
            )

    # 提取总金额和积分
    totals_div = soup.find('div', string=re.compile(r'Totals:'))
    totals_text = totals_div.get_text(strip=True)
    amount = re.search(r'(\d+,?\d*)\$', totals_text).group(1).replace(',', '')
    points = re.search(r'(\d+)\s+Points', totals_text).group(1)

    # 初始化赛事数据存储
    regional_data = {}
    international_data = {}

    # 分别处理两个赛事类型
    for div in soup.find_all('div', class_=re.compile(r'flex flex-col gap-1')):
        # 嵌套标签，不要使用正则，直接遍历即可
        for event_div in div.find_all('div'):
            text = event_div.get_text(strip=True)

            # 确定赛事类型
            if 'Regional' in text:
                current_data = regional_data
                event_type = "Regional Top 8"
            elif 'International' in text:
                current_data = international_data
                event_type = "International Top 8"
            else:
                continue

            # 提取数值
            numbers = re.findall(r'\d+', text)
            current_data['top8'] = numbers[1]
            current_data['wins'] = numbers[2] if len(numbers) > 2 else '0'

    return [
        f"卡组核心：{name}",
        f"累计奖金：{amount}$ | 积分：{points} ",
        f"地区赛入围前8次数: {regional_data['top8']}, 夺冠次数：{regional_data['wins']}",
        f"国际赛入围前8次数: {international_data['top8']}, 夺冠次数：{international_data['wins']}",
    ] + core_cards_format_list

def extract_deck_name(soup: BeautifulSoup) -> str:
    """提取卡组名称"""
    name_tag = soup.find('h1', class_='name')
    return name_tag.get_text(strip=True) if name_tag else "未知卡组"


async def extract_deck_core_new(html_str):
    soup = BeautifulSoup(html_str, 'html.parser')

    # 提取基础信息
    name = extract_deck_name(soup)
    totals = extract_totals(soup)
    tournament_data = extract_tournament_data(soup)
    best_player_decks = extract_best_player_decks(soup)
    # 处理核心卡牌
    core_cards = await process_core_cards(soup)

    # 组装最终结果
    return assemble_results(name, totals, tournament_data, core_cards, best_player_decks)

def extract_totals(soup: BeautifulSoup) -> Dict[str, str]:
    """提取总金额和积分"""
    totals_div = soup.find('div', string=re.compile(r'Totals:'))
    if not totals_div:
        return {'amount': '0', 'points': '0'}

    totals_text = totals_div.get_text(strip=True)
    amount_match = re.search(r'(\d+,?\d*)\$', totals_text)
    points_match = re.search(r'(\d+)\s+Points', totals_text)

    return {
        'amount': amount_match.group(1).replace(',', '') if amount_match else '0',
        'points': points_match.group(1) if points_match else '0'
    }


def extract_tournament_data(soup: BeautifulSoup) -> Dict[str, Dict]:
    """提取赛事数据"""
    tournament_data = {
        'regional': {'top8': '0', 'wins': '0'},
        'international': {'top8': '0', 'wins': '0'}
    }

    for div in soup.find_all('div', class_=re.compile(r'flex flex-col gap-1')):
        for event_div in div.find_all('div'):
            text = event_div.get_text(strip=True)
            if 'Regional' in text:
                update_tournament_data(text, tournament_data['regional'])
            elif 'International' in text:
                update_tournament_data(text, tournament_data['international'])

    return tournament_data


def update_tournament_data(text: str, target: Dict):
    """更新具体赛事数据"""
    numbers = re.findall(r'\d+', text)
    if len(numbers) >= 2:
        target['top8'] = numbers[1]
        target['wins'] = numbers[2] if len(numbers) > 2 else '0'


async def process_core_cards(soup: BeautifulSoup) -> List[str]:
    """处理核心卡牌数据"""
    core_card_href = {}
    core_cards_format_list = []

    # 提取核心卡牌信息
    for ele in soup.find_all(class_='core-card'):
        href = ele.find(class_='flex').get('href', None)
        data_tooltip = ele.find(class_='share').get('data-tooltip', None)
        if href and data_tooltip:
            core_card_href[href] = data_tooltip

    # 批量获取卡牌详情
    if core_card_href:
        card_detail_dict = await batch_fetch_decks([key for key in core_card_href.keys()], concurrency=5)
        for href, detail in card_detail_dict.items():
            if detail:
                core_cards_format_list.append(
                    f"{detail['name']} | 使用统计 :{core_card_href[href]}"
                )

    return core_cards_format_list or ["暂时无法展示核心构筑卡，没有足够的对战数据支持"]

def extract_best_player_decks(soup: BeautifulSoup) -> dict[str, list]:
    """提取顶尖玩家的姓名、名次、卡组链接（只提取公布卡组链接的玩家数据）"""
    sub_heading_content = False
    finalDic = {}
    for tr in soup.find("table", class_="data-table striped").find_all("tr"):
        sub_heading = tr.find("th", class_="sub-heading")
        if sub_heading is not None:
            sub_heading_content = sub_heading.get_text(strip=True)
            finalDic[sub_heading_content] = []
            continue
        if not sub_heading_content:
            continue
        deck_href = tr.find_all("td")[-1]
        if deck_href.find('a') is None:
            continue
        finalDic[sub_heading_content].append([tr.find_all("td")[1].get_text(strip=True),
                                         tr.find_all("td")[3].find("a").get_text(strip=True),
                                         deck_href.find('a').get("href", None)])
    return finalDic

def assemble_results(name: str, totals: Dict,
                     tournament_data: Dict, core_cards: List, best_player_decks: Dict) -> List[str]:
    """组装最终结果"""
    best_player_decks_format_list = []
    for key,value in best_player_decks.items():
        best_player_decks_format_list.append(f"赛事：{key}")
        if len(value) == 0:
            best_player_decks_format_list.append("该赛事玩家均不公开卡组")
            continue
        for ele in value:
            best_player_decks_format_list.append(f"名次：{ele[0]} | 玩家：{ele[1]} | 卡组：https://limitlesstcg.com{ele[2]}")
    return [
        f"卡组核心：{name}",
        f"累计奖金：{totals['amount']}$ | 积分：{totals['points']} ",
        f"地区赛入围前8次数: {tournament_data['regional']['top8']}, 夺冠次数：{tournament_data['regional']['wins']}",
        f"国际赛入围前8次数: {tournament_data['international']['top8']}, 夺冠次数：{tournament_data['international']['wins']}",
    ] + core_cards + best_player_decks_format_list

def extract_deck_code(html_str):
    soup = BeautifulSoup(html_str, 'html.parser')
    final_format_str = ''

    for column in soup.select(".decklist-column"):
        decklist_column_heading = column.select_one(".decklist-column-heading").get_text()
        match = re.search(r'(.+?)\s*\((\d+)\)', decklist_column_heading, re.UNICODE)
        final_format_str += f"{match.group(1)}: {match.group(2)}\n"
        for column_card in column.select(".decklist-card"):
            single_card_list = []
            data_set = column_card.get("data-set", None)
            data_number = column_card.get("data-number", None)
            card_count = column_card.select_one(".card-count").get_text()
            card_name = column_card.select_one(".card-name").get_text()
            single_card_list.append(card_count)
            single_card_list.append(card_name)
            single_card_list.append(data_set)
            single_card_list.append(data_number)
            final_format_str += " ".join(single_card_list)
            final_format_str += "\n"
        final_format_str += "\n"
    return final_format_str[:-2]

@mcp.tool()
async def get_pokemon_rank(format: str = 'SVI-JTG',
                     time: str = 'all',
                     rank: str = 'points',
                     variants: str = 'false',
                     page: int = 1
) -> str:
    """分页获取宝可梦热门卡组排行,一页最大25个
    Args:
        format: 默认为'SVI-JTG'
        time: 卡组排名时间，默认为‘all’
        rank: 排名方式，默认为‘points’
        variants: 是否区分卡组变体，默认为'false'
        page: 分页参数，默认为第一页
    """
    data = await fetch_pokemon_decks(format, time, rank, variants, page)
    return extract_pokemon_data(data)

@mcp.tool()
async def get_tools_drawcalc(basics: int,
                             good: int = 0,
                             bad: int = 0,
                             outs: int = 0):
    """宝可梦游戏对战开局抽牌概率模拟工具
    Args:
        basics: 卡组中的基础宝可梦数量， basics>=0
        good: 卡组中的优质基础宝可梦数量，默认为0 good <= basics
        bad: 卡组中的劣质基础宝可梦数量，默认为0 bad <= basics
        outs: 卡组中的支援者卡数量，默认为0
    """
    if good > basics or bad > basics:
        return 'good或bad数量不应大于basics'
    if outs > 60:
        return 'outs数量不应大于60'
    result = await fetch_tools_drawcalc(basics, good, bad, outs)
    return format_tools_drawcalc(result)

@mcp.tool()
async def get_decks_core(decks_url: str):
    """查询宝可梦卡组的构筑核心和综合表现

    该工具查询宝可梦卡组赛事综合表现、核心构筑卡数据、顶尖玩家的详细卡组链接

    Args:
        decks_url: 构筑链接，格式参考 f"https://limitlesstcg.com/decks/{number}"
    """
    html_str = await fetch_deck_core(decks_url)
    finalResult = await extract_deck_core_new(html_str)
    return '/n'.join(finalResult)

@mcp.tool()
async def export_decks_code(deck_list_url: str):
    """导出卡组代码，用于游戏内复制卡组

    Args:
        deck_list_url: 卡组链接，格式参考 f"https://limitlesstcg.com/decks/list/{number}"
    """
    html_str = await fetch_deck_list(deck_list_url)
    finalStr = extract_deck_code(html_str)
    return finalStr

@mcp.prompt()
def get_pokemon_rank_prompt(
        format: str = 'SVI-JTG',
        time: str = 'all',
        rank: str = 'points',
        variants: str = 'false',
        page: int = 1,
        limit: int = 10
) -> list[base.Message]:
    """生成宝可梦卡组查询(get_pokemon_rank)对话模板"""
    return [
        base.UserMessage(f"查询宝可梦卡组，翻译并保留核心宝可梦的中文译名，保留原始英文，不对卡组链接进行markdown渲染"),
        base.AssistantMessage(
            f"好的，正在为您查询宝可梦卡组，请稍等...\n"
            f"查询卡组{'不区分变体' if variants == 'false' else '区分变体'}。"
            f"保留前{limit}个卡组"
        )
    ]

@mcp.prompt()
def get_tools_drawcalc_prompt(
        basics: int,
        good: int = 0,
        bad: int = 0,
        outs: int = 0
) -> list[base.Message]:
    """生成宝可梦游戏对战开局抽牌概率模拟工具(get_tools_drawcalc)对话模板"""
    return [
        base.UserMessage(f"查询宝可梦游戏对战开局抽牌各事件概率\n"
                         f"卡组中的基础宝可梦数量{basics}\n"
                         f"卡组中的优质基础宝可梦数量{good}\n"
                         f"卡组中的劣质基础宝可梦数量{bad}\n"
                         f"卡组中的支援者卡数量{outs}\n"
        )
    ]

if __name__ == "__main__":
    # Initialize and run the server
    # mcp.settings.port = 8080
    mcp.run(transport='stdio')